//
//  CalculatorEngine.m
//  iCalculator
//
//  Created by Stephen Houser on 9/7/07.
//  Copyright 2007 __MyCompanyName__. All rights reserved.
//

#import "CalculatorEngine.h"

@implementation CalculatorEngine

/* 
 * Initialize the engine, clear any pertinent "registers" and get
 * ready for calculations.
 */
-(id)init {
	[self enterClearAll];
	return self;
}

/*
 * Returns the current operation result from the engine.
 * In effect, if this returns the accumulator unless there
 * have been no operations (no results) since clear. In that
 * case, we return the most recent number entered, from registerX.
 */
- (NSString *) getCurrentResult {
	if (isResult == YES) {
		return [[NSString alloc] initWithFormat: @"%g", registerA];
	} else {
		return [[NSString alloc] initWithFormat: @"%g", registerX];
	}
}

/*
 * Interface to the keypad and "controllers." This routine accepts "commands"
 * (loosely -- numbers and operators) from the controller of the engine. This is
 * where keys get sent in to be processed. Some keys produce changes in the
 * accumulator, memory, etc. So somebody calling this should really think
 * about calling "getCurrentResults" sometime afterwards to see what things look like.
 * Though if you were a program, you might not want all those intermediate results.
 * In reality, this is a dispatcher routine. The real work is done in the individual
 * routines that are called by it.
 */
- (void) sendCommand: (NSString *)command {
	NSLog(@"CalculatorEngine sendCommand: [%@]", command);
	
	// I really hate how this is done here.
	// I forsee a table lookup in your future...
	if ([command isEqualToString: @"c"]) {
		[self enterClearAll];
	} else if ([command isEqualToString: @"ce"]) {
		[self enterClearEntry];
	} else if ([command isEqualToString: @"+"]) {
		[self enterBinaryOperator: command];
	} else if ([command isEqualToString: @"-"]) {
		[self enterBinaryOperator: command];
	} else if ([command isEqualToString: @"*"]) {
		[self enterBinaryOperator: command];
	} else if ([command isEqualToString: @"/"]) {
		[self enterBinaryOperator: command];
	} else if ([command isEqualToString: @"%"]) {
		[self enterPercent];
	} else if ([command isEqualToString: @"="]) {
		[self enterEquals];
	} else if ([command isEqualToString: @"mc"]) {
		[self memoryClear];
	} else if ([command isEqualToString: @"mr"]) {
		[self memoryRecall];
	} else if ([command isEqualToString: @"m+"]) {
		[self memoryAdd];
	} else if ([command isEqualToString: @"m-"]) {
		[self memorySubtract];
	} else {
		// catches everything else -- even things it should not
		// the decimal point is good to collect, but... not other
		// things.
		[self enterNumber: command];
	}
}

/*
 * Internal routine to push a number onto the operating "stack."
 * Called by sendCommand to deal with number entry.
 */
- (void) enterNumber: (NSString *) number {
	[self dump: @">enterNumber"];

	// if there is no result in the A register then
	// this is not a chained expression
	// if there is, then A should stay for the calculation
	if (isResult == NO) {
		registerA = registerX;
	}

	isResult = NO;
	registerX = atof([number UTF8String]);	
		
	[self dump: @"<enterNumber"];	
}

/*
 * Internal routine called when a binary operator is entered.
 * This routine sets up the "pending operaton" as the operator
 * that was given. In addition, it will resolve any pending
 * operations before doing its work.
 */
- (void) enterBinaryOperator: (NSString *)operator {
	[self dump: @">enterbinaryOperator"];

	// resolve prior pending operations, update accumulator w/results.
	if (pendingOperation != Nil) {
		[self evaluateOperation];
		isResult = YES;
	}
	
	// set up for this operation later... after we get
	// the other number (binary 'eh).
	pendingOperation = operator;
	
	[self dump: @"<enterBinaryOperator"];
}

/*
 * Internal routine to evaluate what has been entered. Triggered by the "=" operaton.
 * Carries out (resolves) any pending operations and updates the accumulator with results.
 */
- (void) enterEquals {
	[self dump: @">enterEquals"];
	
	[self evaluateOperation];
	isResult = YES;
	
	[self dump: @"<enterEquals"];
}

/* Percent acts like equals, after it converts x to a percentage of a */
/* 100 + 9 % = 109 */
- (void) enterPercent {
	[self dump: @">enterPercent"];

	// Option 1 -- act like equals was pressed - my handheld does it this way
	//registerX = (registerA / 100.0) * registerX;
	//[self evaluateOperation];
	
	// Option 2 -- act like apple calculator and just jam the percent into display
	registerX = (registerA / 100.0) * registerX;
	
	[self dump: @"<enterPercent"];
}

/*
 * Internal Operation to clear the display register and
 * any state associalted with it.
 */
- (void) enterClearEntry {
	[self dump: @">clearEntry"];

	// is this really even needed as entry is
	// all done in the view... hummm...

	[self dump: @"<clearEntry"];
}

/*
 * Internal operation to clear registers and reset engine
 * to initial entry state.
 */
- (void) enterClearAll {
	[self dump: @">clear"];

	// reset registers
	registerX = 0.0f;
	registerA = 0.0f;

	// reset operations
	pendingOperation = Nil;
	lastOperation = Nil;

	// reset state
	isResult = NO;
		
	[self dump: @"<clear"];
}

/*
 * Clear the contents of the memory register.
 */
- (void) memoryClear {
	[self dump: @">memoryClear"];

	memory = 0.0f;

	[self dump: @">memoryClear"];
}

/*
 * Add the display register to the memory register.
 * The result goes to memory not the display.
 */
- (void)memoryAdd {
	[self dump: @">memoryAdd"];

	memory = memory + registerX;

	[self dump: @"<memoryAdd"];
}

/*
 * Subtract the display register to the memory register.
 * The result goes to memory not the display.
 */
- (void)memorySubtract {
	[self dump: @">memorySubtract"];

	memory = memory - registerX;

	[self dump: @"<memorySubtract"];
}

/*
 * Recall the memory register to the display register.
 */
- (void)memoryRecall {
	[self dump: @">memoryRecall"];

	// the accumulator is where getCurrentResult is pulled form
	// so when we want to recall memory, toss it there.
	// TODO memoryRecall needs some thought so it works with 2+3+mr= and the like 
	// should it be there as a result?
	registerA = memory;
	isResult = YES;
	
	[self dump: @"<memoryRecall"];
}

/*
 * Internal routine to resolve a pending operation by evaluating
 * the operation and the current operands (X and A). Places results
 * into the A register. Actually, this is just a dispatcher routine
 * which farms out to operationXXX routines. No work here, move along.
 */
- (void) evaluateOperation {
	[self dump: @">evaluateOperaton"];
	
	// Resolve the pending operation by displatching
	// all operations should do: registerA = registerX op registerA
	// thus, results are left in the accumulator.
	if ([pendingOperation isEqualToString: @"+"]) {
		[self operationAdd];
	} else if ([pendingOperation isEqualToString: @"-"]) {
		[self operationSubtract];
	} else if ([pendingOperation isEqualToString: @"*"]) {
		[self operationMultiply];
	} else if ([pendingOperation isEqualToString: @"/"]) {
		[self operationDivide];
	}

	// do i need this?
	if (lastOperation != Nil) {
		[lastOperation release];
	}
	
	// update last operation to be this one
	lastOperation = pendingOperation;
	
	// clear pending operation, it has been resolved.
	pendingOperation = Nil;

	[self dump: @"<evaluateOperaton"];
}

/*
 * Perform an add operation on the accumulator and display registers.
 */
- (void) operationAdd {
	[self dump: @">add"];
	
	registerA = registerA + registerX;

	[self dump: @"<add"];
}

/*
 * Perform a subtraction operation on the accumulator and display registers.
 */
- (void) operationSubtract {
	[self dump: @">subtract"];

	registerA = registerA - registerX;

	[self dump: @"<subtract"];
}

/*
 * Perform a multiplicaton operation on the accumulator and display registers.
 */
- (void) operationMultiply {
	[self dump: @">multiply"];

	registerA = registerA * registerX;

	[self dump: @"<multiply"];
}

/*
 * Perform a division operation on the accumulator and display registers.
 */
- (void) operationDivide {
	[self dump: @">divide"];

	registerA = registerA / registerX;

	[self dump: @"<divide"];
}

/* 
 * Utility method to dump diagnostic information to "console"
 */
- (void) dump: (NSString *) message {
	// TODO command line option to turn on and off log messages and verbosity.
/*
	// Multi-lne version
	NSLog(message);
	NSLog(@"\tX                : %@", registerX);
	NSLog(@"\tA			       : %g", registerX);
	NSLog(@"\tmemory           : %@", memory);
	NSLog(@"\tpendingOperation : %@", pendingOperation);
	NSLog(@"\tlastOperation    : %@", lastOperation);
	NSLog(@"\tisResult         : %@", (isResult ? @"YES" : @"no"));
*/
	// One-line version
	NSLog(@"x=%g\ta=%g\tm=%g\t(%@)\top=(%@,%@)\t%@", 
		registerX, registerA, memory,
		(isResult ? @"RESULT" : @"result"),		
		pendingOperation, lastOperation,
		message
		);
}

@end
